Debug JavsScript 的時候最簡單直覺的方式就是 console.log
,不過除了 log 以外,Console API 其實還有非常多好用的 Method,來看看 Console 家族中有哪些厲害的成員吧。
大部分的時候
console.log
都能解決問題,不過用對 Method 可以省去更多時間。
它的作用和 console.log
差不多,最大的差別是當第一個參數是 falsy
時才會作用。
[false, null, undefined, 0, -0, 0n, NaN, ""]
為了檢查 user
物件的 name
屬性有沒有問題,可能會寫出以下的程式碼來 Debug:
const user = {
name: ""
};
if (!user.name) {
console.log('哪邊出錯了QQ', user);
}
這種狀況使用 console.assert
就不需要另外加入 if
判斷式,不但寫了更少程式碼,在語意上也很清晰:「條件不符就拋錯。」:
console.assert(user.name, '哪邊出錯了QQ', user);
唯一要注意的就是第一個參數必須是 falsy
值才會出現 error,條件寫反了甚麼事情都不會發生。
console.count(label)
會印出這個標籤被執行了幾次,預設值是 default
,可以用在快速的計數。
可以用以下的程式碼試試 console.count
的效果:
function count(arg) {
console.count(arg);
}
count('foo');
count('bar');
count('bar');
也能用來檢查多種狀況的出現次數:
for (let i = 0; i < 5; i++) {
const int = Math.ceil(Math.random() * 100);
if (int < 20) console.count('太高了');
if (int > 20) console.count('太低了');
}
與 count
相生,用來歸零,可用在計算單次行為的觸發的計數,例如想在 React 中計算按下按鈕後總共觸發了幾次 Render。
同步的狀況下 Event handler 內的 setState
會被 React Batch 在一起,但非同步時每個 setState
都會觸發 Render,因此以下範例在點擊按鈕後會觸發 3 次 Render。
function App() {
const [count, setCount] = React.useState(0);
const [count2, setCount2] = React.useState(0);
const [asyncCount, setAsyncCount] = React.useState(0);
const [asyncCount2, setAsyncCount2] = React.useState(0);
const onClick = () => {
console.countReset('render'); // 計算前先把 'render' 歸零
setCount(count + 1); // 1
setCount2(count2 + 1); // 1
Promise.resolve().then(() => {
setAsyncCount(asyncCount + 1); // 2
setAsyncCount2(asyncCount2 + 1); // 3
})
}
console.count('render');
return (
<div onClick={onClick}>
<h1>Hello, please click me.</h1>
<h2>{count}</h2>
</div>
);
}
或是到 Demo 頁面 React Render Counter 打開 Console 面板看看
為了在一大堆混亂的訊息中一眼看到自己的 log,是否曾經寫出這樣的程式碼呢?
console.log('---------');
console.log(object);
console.log('---end---');
雖然 ---
是很顯眼沒錯,但其實有更好的做法,用console.group
可以自訂 Message group 的標籤也可以多層嵌套,並用 console.groupEnd
來關閉 Group:
console.group('Start debugging');
console.log('de-');
console.group('Nested');
console.warn('deeper message');
console.groupEnd();
console.log('bug');
console.groupEnd();
另外還有 Group 的兄弟 console.groupCollapsed
,只差在預設 Gourp 是閉合的需要手動展開。
如果在這些 Console API 中只能選一個來介紹,那肯定是
console.table
,筆者自己就常常用到它。
需要印出陣列中的物件時,比起直接用 console.log
印出再慢慢展開,console.table
絕對是更好的選擇,來看看以下範例。
const rows = [
{
"name": "Frozen yoghurt",
"calories": 159,
"fat": 6,
"carbs": 24,
"protein": 4
},
{
"name": "Ice cream sandwich",
"calories": 237,
"fat": 9,
"carbs": 37,
"protein": 4.3
},
{
"name": "Eclair",
"calories": 262,
"fat": 16,
"carbs": 24,
"protein": 6
}
];
直接執行 console.log(rows)
會發生什麼事情呢?
這絕對不會是 Debug 時想要看到的東西,需要手動展開物件才能看到內容,如果按住 Option
或 Alt
來一次展開全部屬性呢?
只能展開第一個物件,而且把 __proto__
都給展開了,試試 console.table(rows)
會印出甚麼結果:
相較 console.log
直接印出物件本身,console.table
會以表格來印出物件內容,一次顯示更多資訊,另外可以用參數改變顯示的欄位以及拖拉調整欄位的寬度。
console.table(rows, ['name', 'fat']);
除了顯示上更為清楚外,console.table
還解決了另一個問題,試試在 Console 中執行以下程式碼,首先宣告一個物件 animal
,以 console.log
印出後再修改物件的屬性:
const animal = {
name: 'mimi',
type: 'cat',
other: {
emoji: '?',
sound: 'meow'
}
};
console.log(animal);
animal.name = 'ami';
執行後會再手動展開物件出現以下結果:
可以看到展開前後的 name
屬性值是不同的,由於執行 console.log
的當下還沒修改 animal
的內容,正確顯示了執行 console.log
當下的物件值,也是一般預期想看到的值,但手動展開物件的時候值已經改變了,而使用 console.table
的話正好能避開這種困惑的情況。
不過這樣的行為其實不是個問題,執行 console.log
時右上角會有一個小圖示,Hover 上去會看到提示 Value below was evaluated just now.
,說明了看到的是展開當下物件的值。
同場佳映:當物件內容較深的時候,JSON.stringify(animal, null, 2)
也是不錯的選擇,直接將物件轉為 JSON 字串全部顯示。
想要測量如使用者行為或是 Function 執行的時間的話,很常看到一種方式 -- 算數學:
const t0 = performance.now();
alert('Hello World!');
const t1 = performance.now();
alert('Another Hello World!');
console.log(`Spent: ${t1 - t0} ms`);
const t2 = performance.now();
console.log(`Spent: ${t2 - t0} ms`);
想要快速測試時間還寫了這堆程式碼實在有點惱人,用 console.time
來改寫一下,和 console.group
一樣可以傳入標籤參數來識別計時器:
console.time('Spent');
alert('Hello World!');
console.timeLog('Spent');
alert('Another Hello World!');
console.timeEnd('Spent');
用法非常簡單,用 time
啟動計時器後可用無限個 timeLog
來印出目前過了多久時間,最後用 timeEnd
來停止計時器,如果在 timeLog
或 timeEnd
中放入未啟動的標籤會噴 Warning。
如果出問題的部分和其他套件有關係,尤其是一個 Function 會在多處被使用的時候,有別於 console.log
只能得知執行當下程式碼的位置,console.trace
會印出 Call stack 並直接展開,能更快速看出問題:
function a() {
console.trace();
}
function b() {
a();
}
function c() {
b();
}
b()
c()
筆者曾經在使用影片播放器套件的時候,不知道是不是自己寫壞了,有時影片會突然暫停,那時就用了類似以下的程式碼來檢查,馬上就看出是套件中某個 Function 觸發了暫停。
video.addEventListener('pause', console.trace);
今天講解的 Console API 有許多個都是筆者愛用的 Debug 方式,確實節省了不少時間呢,明天將會講解可在 Console 面板中使用,但不屬於 Console API 的內建 Debug Function。